﻿using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace TestClient_console.Core
{
    public static class RemotePhysics
    {
        public static TcpClient client = null;
        public static PhysicsUser pUser = null;
        public static List<RemoteBody> rBodies = new List<RemoteBody>();
        public const int RECONNECT_TIME = 5 * 1000;
        public static bool bWaitingForReconnect = false;

        public struct OPCodes
        {
            public static byte[] handshakeOpcode = new byte[4] { 0x1, 0x1, 0x1, 0x1 };
            public static byte[] handshakeResponseOpcode = new byte[4] { 0x1, 0x1, 0x1, 0x2 };
            public static byte[] requestWorldInitOpcode = new byte[4] { 0x1, 0x1, 0x1, 0x3 };
            public static byte[] requestWorldInitResponseOpcode = new byte[4] { 0x1, 0x1, 0x1, 0x4 };
            public static byte[] worldUpdateOpcode = new byte[4] { 0x1, 0x1, 0x1, 0x5 };
        }

        public static void Connect(string IP = "127.0.0.1", int port = 8647)
        {
            try
            {
                bWaitingForReconnect = false;
                StaticHelpers.Log(String.Format("Connecting to server @ {0}:{1} (default 127.0.0.1:8647)", IP, port.ToString()));
                client = new TcpClient();
                client.Connect(new IPEndPoint(IPAddress.Parse(IP), port));
                StaticHelpers.Log("Connected to server! Initializing handshake process...");

                NetworkStream networkStream = client.GetStream();

                byte[] buffer = new byte[client.ReceiveBufferSize];

                networkStream.BeginRead(buffer, 0, buffer.Length, ReadCallback, buffer);
            }
            catch(SocketException ex)
            {
                throw ex;
            }


            Handshake();
        }

        static void ProcessBuffer(byte[] buffer)
        {
            switch (buffer[0])
            {
                case 0x1:
                    if (buffer[1] == 0x1)
                    {
                        StaticHelpers.Log(Encoding.UTF8.GetString(buffer, 2, buffer.Length));
                    }
                    else if (buffer[1] == 0x2)
                    {
                        Array.Resize<byte>(ref buffer, 6);
                        lock (buffer)
                        {
                            byte[] msg = buffer.Skip(2).ToArray();

                            if (msg.SequenceEqual(OPCodes.handshakeResponseOpcode))
                            {
                                StaticHelpers.Log("Handshake successfull!");
                                pUser.handshaken = true;

                                StaticHelpers.SendByteMsg(OPCodes.requestWorldInitOpcode);
                            }
                        }

                    }

                    else if(buffer[1] == 0x3)
                    {
                        Dictionary<int, Vector2> data = new Dictionary<int, Vector2>();

                        data = GetWorldBodiesPairs(buffer);
                        StaticHelpers.Log("World init received!");
                        foreach(KeyValuePair<int, Vector2> pair in data)
                        {
                            rBodies.Add(new RemoteBody(pair.Key, pair.Value));

                            StaticHelpers.Log(String.Format("Object ID = {0}, position = {1} added!", pair.Key.ToString(), pair.Value.ToString()));
                        }
                    }
                    else if (buffer[1] == 0x4)
                    {
                        Dictionary<int, Vector2> data = new Dictionary<int, Vector2>();

                        data = GetWorldBodiesPairs(buffer);
                        StaticHelpers.Log("Update!");
                        foreach (KeyValuePair<int, Vector2> pair in data)
                        {
                            foreach (RemoteBody b in rBodies)
                            {
                                if (b.ID == pair.Key)
                                {
                                    b.position = pair.Value;
                                    StaticHelpers.Log(String.Format("Object ID = {0}, position = {1} updated!", b.ID.ToString(), b.position.ToString()));
                                }                        
                            }
                        }                        
                    }

                    break;
            }
        }

        static Dictionary<int, Vector2> GetWorldBodiesPairs(byte[] buffer)
        {
            Dictionary<int, Vector2> data = new Dictionary<int, Vector2>();

            int intSize = sizeof(int);
            int floatSize = sizeof(float);
            byte[] msg = buffer.Skip(2).ToArray();
            if (msg.Take(4).ToArray().SequenceEqual(OPCodes.requestWorldInitResponseOpcode) || msg.Take(4).ToArray().SequenceEqual(OPCodes.worldUpdateOpcode))
            {
                msg = msg.Skip(4).ToArray();
                byte[] countBts = new ArraySegment<byte>(msg, 0, intSize).Array;
                int count = BitConverter.ToInt32(countBts, 0);
                msg = msg.Skip(intSize).ToArray();
                List<int> ids = new List<int>();
                List<Vector2> positions = new List<Vector2>();

                for (int i = 0; i < count; i++)
                {
                    ids.Add(BitConverter.ToInt32(msg.Take(intSize).ToArray(), 0));
                    msg = msg.Skip(intSize).ToArray();
                }
                for (int i = 0; i < count; i++)
                {
                    Vector2 res = Vector2.Zero;
                    byte[] tempBuff = msg.Take(2 * floatSize).ToArray();
                    byte[] X = tempBuff.Take(floatSize).ToArray();
                    byte[] Y = tempBuff.Skip(floatSize).ToArray();
                    float fX = BitConverter.ToSingle(X, 0);
                    float fY = BitConverter.ToSingle(Y, 0);
                    res = new Vector2(fX, fY);
                    positions.Add(res);
                    msg = msg.Skip(2 * floatSize).ToArray();
                }

                for (int k = 0; k < ids.Count; k++)
                {
                    data.Add(ids[k], positions[k]);
                }
            }

            return data;
        }

        static void ReadCallback(IAsyncResult result)
        {
               
            NetworkStream networkStream;

            try
            {
                networkStream = client.GetStream();         
            }
            catch(Exception ex)
            {
                throw ex;
            }         

            byte[] buffer = result.AsyncState as byte[];

            ProcessBuffer(buffer);


            try
            {
                networkStream.BeginRead(buffer, 0, buffer.Length, ReadCallback, buffer);
            }
            catch(IOException ex)
            {
                throw ex;

            }
        }

        static void ClearAll()
        {
            if(client.Connected)
                client.Close();

            client = null;
            rBodies = new List<RemoteBody>();
            pUser = null;
        }

        static void CreatePhysicsUser()
        {
            pUser = new PhysicsUser();

            StaticHelpers.Log("PhysicsUser created");
        }

        static void SendInitData(PhysicsUser user)
        {
            StaticHelpers.SendString("Ahoj");
            Thread.Sleep(50);
            StaticHelpers.SendByteMsg(new byte[4] { 0x1, 0x1, 0x1, 0x1 });

        }

        static void Handshake()
        {
            StaticHelpers.Log("Creating PhysicsUser");
            CreatePhysicsUser();
            SendInitData(pUser);
        }

    }
}
